/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Xml;
using System.Xml.XPath;
using DaveSexton.DocProject.Sandcastle.Resources;

namespace DaveSexton.DocProject.Sandcastle.Configuration
{
	public enum XmlNodeTypeReference
	{
		None, 
		Self,
		/// <remarks>
		/// This value is only valid when the X-path expression evaluates to one ore more <i>element</i> nodes.
		/// </remarks>
		Children
	}

	public sealed class ConfigChangeElement : ConfigurationElement
	{
		#region Public Properties
		[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
		public string Name
		{
			get
			{
				return this["name"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this["name"] = value;
			}
		}

		[ConfigurationProperty("version", DefaultValue = "All", IsRequired = false)]
		public HelpTypes Version
		{
			get
			{
				return (HelpTypes?) this["version"] ?? HelpTypes.All;
			}
			set
			{
				this["version"] = value;
			}
		}

		[ConfigurationProperty("presentationName", DefaultValue = "*", IsRequired = false)]
		[StringValidator(MinLength = 1)]
		public string PresentationName
		{
			get
			{
				return this["presentationName"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this["presentationName"] = value;
			}
		}

		[ConfigurationProperty("replaceWith", IsRequired = false)]
		public string ReplaceWith
		{
			get
			{
				return this["replaceWith"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this["replaceWith"] = value;
			}
		}

		/// <summary>
		/// Inserts a sibling node after the current node.
		/// </summary>
		[ConfigurationProperty("insertAfter", IsRequired = false)]
		public string InsertAfter
		{
			get
			{
				return this["insertAfter"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this["insertAfter"] = value;
			}
		}

		/// <summary>
		/// Inserts a sibling node before the current node.
		/// </summary>
		[ConfigurationProperty("insertBefore", IsRequired = false)]
		public string InsertBefore
		{
			get
			{
				return this["insertBefore"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this["insertBefore"] = value;
			}
		}

		/// <summary>
		/// Appends a child node.
		/// </summary>
		[ConfigurationProperty("append", IsRequired = false)]
		public string Append
		{
			get
			{
				return this["append"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				this["append"] = value;
			}
		}

		/// <summary>
		/// Removes one or more nodes.
		/// </summary>
		[ConfigurationProperty("remove", IsRequired = false)]
		public XmlNodeTypeReference Remove
		{
			get
			{
				return (XmlNodeTypeReference) this["remove"];
			}
			set
			{
				this["remove"] = value;
			}
		}

		[ConfigurationProperty("options", IsRequired = false)]
		public System.Text.RegularExpressions.RegexOptions Options
		{
			get
			{
				return ((System.Text.RegularExpressions.RegexOptions?) this["options"]).GetValueOrDefault(
					System.Text.RegularExpressions.RegexOptions.None);
			}
			set
			{
				this["options"] = value;
			}
		}

		[ConfigurationProperty("regex", IsRequired = false)]
		public string Regex
		{
			get
			{
				return this["regex"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (XPath != null)
					throw new InvalidOperationException(Errors.ConfigReplacementRegExOrXPath);

				this["regex"] = value;
			}
		}

		[ConfigurationProperty("xpath", IsRequired = false)]
		public string XPath
		{
			get
			{
				return this["xpath"] as string;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (Regex != null)
					throw new InvalidOperationException(Errors.ConfigReplacementRegExOrXPath);

				this["xpath"] = value;
			}
		}

		[ConfigurationProperty("fragment", IsRequired = false)]
		public bool Fragment
		{
			get
			{
				return (bool?) this["fragment"] ?? false;
			}
			set
			{
				this["fragment"] = value;
			}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ConfigChangeElement" /> class.
		/// </summary>
		public ConfigChangeElement()
		{
		}
		#endregion

		#region Methods
		private static void WriteXml(XmlWriter writer, string xml)
		{
			try
			{
				XmlDocument document = new XmlDocument();
				document.PreserveWhitespace = true;
				document.LoadXml("<root>" + xml + "</root>");

				// NOTE: WriteRaw is not supposed to escape XML, however it does when used here.  The workaround is to load the XML into a document and use WriteNode instead.
				foreach (XPathNavigator navigator in document.DocumentElement.CreateNavigator().SelectChildren(XPathNodeType.All))
					writer.WriteNode(navigator, false);
			}
			finally
			{
				writer.Close();
			}
		}

		public string Apply(string xml)
		{
			if (xml == null)
				throw new ArgumentNullException("xml");

			if (string.IsNullOrEmpty(Regex))
				return xml;

			return System.Text.RegularExpressions.Regex.Replace(xml, Regex, ReplaceWith, Options);
		}

		public void Apply(IXPathNavigable document)
		{
			if (document == null)
				throw new ArgumentNullException("document");

			if (string.IsNullOrEmpty(XPath))
				return;

			XPathNavigator navigator = document.CreateNavigator();

			navigator = navigator.SelectSingleNode(XPath);

			if (navigator != null)
			{
				if (!string.IsNullOrEmpty(ReplaceWith))
				{
					if (navigator.NodeType == XPathNodeType.Attribute)
						navigator.SetValue(ReplaceWith);
					else if (!Fragment)
						navigator.OuterXml = ReplaceWith;
					else
					{
						WriteXml(navigator.InsertAfter(), ReplaceWith);
						navigator.DeleteSelf();
					}
				}
				else if (!string.IsNullOrEmpty(Append))
				{
					if (Fragment)
						WriteXml(navigator.AppendChild(), Append);
					else
						navigator.AppendChild(Append);
				}
				else if (!string.IsNullOrEmpty(InsertAfter))
				{
					if (Fragment)
						WriteXml(navigator.InsertAfter(), InsertAfter);
					else
						navigator.InsertAfter(InsertAfter);
				}
				else if (!string.IsNullOrEmpty(InsertBefore))
				{
					if (Fragment)
						WriteXml(navigator.InsertBefore(), InsertBefore);
					else
						navigator.InsertBefore(InsertBefore);
				}
				else
				{
					switch (Remove)
					{
						case XmlNodeTypeReference.Self:
							navigator.DeleteSelf();
							break;
						case XmlNodeTypeReference.Children:
							while (navigator.MoveToFirstChild())
								navigator.DeleteSelf();
							break;
					}
				}
			}
		}
		#endregion
	}
}
